﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Reflection;
using System.Linq;

using Octopus.Injection.Commons.Config;

namespace Octopus.Injection.Commons
{
    public static class ConfigRepository
    {
        public const int AllowSingleton = -1;

        public static Dictionary<string, ServiceBlock> ServiceBlocks { get; set; }
        public static Dictionary<string, Assembly> ServiceTypes { get; set; }
        public static Dictionary<string, KeyValuePair<int, object>> ContractsNames { get; set; }
        public static Dictionary<string, KeyValuePair<int, object>> ContractsTypes { get; set; }
        public static Dictionary<string, object> InjectionMap { get; set; }
        public static Dictionary<string, object> InjectionSingletonMap { get; set; }

        private static Dictionary<string, Assembly> ServiceTypesCrossDomain { get; set; }

        public static void LoadConfiguration()
        {
            var octoSec = ConfigurationManager.GetSection("spock/octopus") as OctopusSection;                 

            if (octoSec != null)
            {
                if (octoSec.ServiceBlocks != null)
                {
                    ServiceBlocks = new Dictionary<string, ServiceBlock>(octoSec.ServiceBlocks.Count);
                    ServiceTypes = new Dictionary<string, Assembly>();
                    ServiceTypesCrossDomain = new Dictionary<string, Assembly>();

                    foreach (ServiceBlockConf svcBlockConf in octoSec.ServiceBlocks)
                    {
                        var serviceBlock = new ServiceBlock(svcBlockConf.Name);
                        var asmDir = svcBlockConf.AssemblyLocation;

                        foreach (DependenceConf dependConf in svcBlockConf.Dependencies)
                        {
                            string fileDll = string.Format("{0}/{1}", asmDir, dependConf.Assembly);

                            Assembly assembly = null;

                            if (dependConf.IsCrossDomain)
                            {
                                AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainAssemblyResolve;
                                assembly = AppDomain.CurrentDomain.Load("@#" + fileDll);
                                ServiceTypesCrossDomain[assembly.FullName] = assembly;
                            }
                            else
                            {
                                assembly = Assembly.LoadFrom(Path.GetFullPath(fileDll));
                                ServiceTypes[assembly.FullName] = assembly;
                            }

                            serviceBlock.Assemblies.Add(assembly);

                            //Cargamos el tipo que se injectará en el servicio
                            if (svcBlockConf.GenericParameter != null &&
                                dependConf.Assembly.ToLower().Equals(svcBlockConf.GenericParameter.Assembly.ToLower()))
                            {
                                serviceBlock.InjectionType = assembly.GetType(svcBlockConf.GenericParameter.Class);
                                serviceBlock.IsGeneric = serviceBlock.InjectionType.IsGenericType;
                            }

                            ServiceBlocks[serviceBlock.Name] = serviceBlock;
                        }
                    }
                }

                ContractsNames = new Dictionary<string, KeyValuePair<int, object>>();
                ContractsTypes = new Dictionary<string, KeyValuePair<int, object>>();
                InjectionMap = new Dictionary<string, object>();
                InjectionSingletonMap = new Dictionary<string, object>();

                if(octoSec.InjectionMap != null)
                {
                    if(octoSec.InjectionMap.Contracts != null)
                    {
                        foreach (Contract contract in octoSec.InjectionMap.Contracts)
                        {
                            object impContract = null;
                            int identifier = contract.Singleton ? AllowSingleton : 0;

                            var asm = string.Empty;
                            var typeName = contract.Class;

                            if (typeName.Contains(","))
                            {
                                asm = typeName.Substring(typeName.IndexOf(',') + 1).Trim();
                                typeName = typeName.Split(',')[0].Trim();
                            }

                            string interfaceFullName = contract.Interface;

                            if (!string.IsNullOrEmpty(contract.Composition))
                            {
                                interfaceFullName += "[";

                                var aTypes = contract.Composition.Split(';').ToList();
                                aTypes.ForEach(n => 
                                {
                                    var asmType = "[" + n + "]";

                                    typeName += asmType;
                                    interfaceFullName += asmType;
                                });

                                interfaceFullName += "]";
                            }

                            var type = new OctopusTypeBinder(true).BindToType(asm, typeName);

                            if(type != null)
                            {
                                impContract = Activator.CreateInstance(type);    
                            }

                            ContractsNames.Add(contract.Name, new KeyValuePair<int, object>(identifier, impContract));
                            ContractsTypes.Add(interfaceFullName, new KeyValuePair<int, object>(identifier, impContract));
                        }
                        
                        foreach (Config.Injection injection in octoSec.InjectionMap.Injections)
                        {
                            if(injection.Singleton)
                            {
                                object single = null;

                                var asm = string.Empty;
                                var typeName = injection.Class;

                                if (typeName.Contains(","))
                                {
                                    asm = typeName.Substring(typeName.IndexOf(',') + 1).Trim();
                                    typeName = typeName.Split(',')[0].Trim();
                                }

                                if (!string.IsNullOrEmpty(injection.Composition))
                                {
                                    var aTypes = injection.Composition.Split(';').ToList();
                                    aTypes.ForEach(n => typeName += "[" + n + "]");
                                }

                                var type = new OctopusTypeBinder(true).BindToType(asm, typeName);

                                if (type != null)
                                {
                                    single = Activator.CreateInstance(type);
                                }

                                InjectionMap.Add(injection.Class, single);
                            }
                        }
                    }
                }
            }
        }

        private static Assembly CurrentDomainAssemblyResolve(object sender, ResolveEventArgs args)
        {
            Assembly assembly = null;

            if (args.Name.StartsWith("@#"))
            {
                var fileDll = args.Name.Substring(2);

                assembly = Assembly.LoadFrom(Path.GetFullPath(fileDll));
            }
            else
            {
                if (ServiceTypesCrossDomain != null)
                {
                    if(ServiceTypesCrossDomain.ContainsKey(args.Name))
                    {
                        assembly = ServiceTypesCrossDomain[args.Name];    
                    }
                    else
                    {
                        assembly = ServiceTypesCrossDomain
                                        .Where(asm => asm.Key.Split(',')[0].Equals(args.Name))
                                        .Select(asm => asm.Value).FirstOrDefault();
                    }

                    if (assembly == null && ServiceTypes != null && ServiceTypes.ContainsKey(args.Name))
                    {
                        assembly = ServiceTypes[args.Name];
                    }
                }
            }

            return assembly;
        }
    }
}
